﻿using System;
using System.Data;
using MySql.Data.MySqlClient;
using System.Text;
using System.Security.Cryptography;
using l1cs.server.utils;
using l1cs.server.datatables;

namespace l1cs.server
{
    /// <summary>
    /// アカウントテーブルを操作するクラス。
    /// </summary>
    public class Account
    {
        /// <summary>アカウント名</summary>
        private String _name;

        /// <summary>接続先のIPアドレス</summary>
        private String _ip;

        /// <summary>パスワード(暗号化されている)</summary>
        private String _password;

        /// <summary>最終アクティブ日</summary>
        private Nullable<DateTime> _lastActive;

        /// <summary>アクセスレベル(GMか？)</summary>
        private int _accessLevel;

        /// <summary>接続先のホスト名</summary>
        private String _host;

        /// <summary>アクセス禁止の有無(Trueで禁止)</summary>
        private bool _banned;

        /// <summary>キャラクターの追加スロット数</summary>
        private int _characterSlot;

        /// <summary>アカウントが有効か否か(Trueで有効)</summary>
        private bool _isValid = false;

        /// <summary>メッセージログ用</summary>
        private static Logger _log = Logger.getLogger( typeof( Account ).FullName );

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        public Account() { }

        /// <summary>
        /// パスワードを暗号化する。
        /// </summary>
        /// <param name="rawPassword">平文のパスワード</param>
        /// <returns>暗号化したパスワード</returns>
        private String encodePassword( String rawPassword )
        {/*TTOK*/
            byte[] buf = Encoding.UTF8.GetBytes( rawPassword );
            //buf = MessageDigest.getInstance( "SHA" ).digest( buf );
            // SHA256のハッシュ値を取得する
            SHA1 crypto = new SHA1CryptoServiceProvider();
            byte[] hashValue = crypto.ComputeHash( buf );

            //return Base64.encodeBytes( buf );
            return System.Convert.ToBase64String( buf );
        }

        /// <summary>
        /// アカウントを新規作成する。
        /// </summary>
        /// <param name="name">アカウント名</param>
        /// <param name="rawPassword">平文パスワード</param>
        /// <param name="ip">接続先のIPアドレス</param>
        /// <param name="host">接続先のホスト名</param>
        public void create( String name, String rawPassword, String ip, String host )
        {/*TTOK*/
            _name = name;
            _password = encodePassword( rawPassword );
            _ip = ip;
            _host = host;
            _banned = false;
            _lastActive = DateTime.Now;
            String sqlstr = @"INSERT INTO accounts SET
                                login          = ?1,
                                password       = ?2,
                                lastactive     = ?3,
                                access_level   = ?4,
                                ip             = ?5,
                                host           = ?6,
                                banned         = ?7,
                                character_slot = ?8";
            using ( MySqlWrapper wrapper = new MySqlWrapper() )
            {
                MySqlCommand cmdins = new MySqlCommand( sqlstr );
                cmdins.Parameters.Add( new MySqlParameter( "?1", name ) );
                cmdins.Parameters.Add( new MySqlParameter( "?2", encodePassword( rawPassword ) ) );
                cmdins.Parameters.Add( new MySqlParameter( "?3", _lastActive ) );
                cmdins.Parameters.Add( new MySqlParameter( "?4", (Object)0 ) );
                cmdins.Parameters.Add( new MySqlParameter( "?5", ip ) );
                cmdins.Parameters.Add( new MySqlParameter( "?6", host ) );
                cmdins.Parameters.Add( new MySqlParameter( "?7", false ) );
                cmdins.Parameters.Add( new MySqlParameter( "?8", (Object)0 ) );
                wrapper.ExecNonQuery( cmdins );
            }
            _log.info( "created new account for " + name );
        }

        /// <summary>
        /// アカウント情報をDBから抽出する。
        /// </summary>
        /// <param name="name">アカウント名</param>
        /// <returns>抽出に成功ならtrue</returns>
        public bool load( String name )
        {/*TTOK*/
            String sqlstr = "SELECT * FROM accounts WHERE login=?1 LIMIT 1";
            using ( MySqlWrapper wrapper = new MySqlWrapper() )
            {
                MySqlCommand cmd = new MySqlCommand( sqlstr );
                cmd.Parameters.Add( new MySqlParameter( "?1", (Object)name ) );
                DataTable dt = wrapper.GetDataTable( cmd );
                if ( dt.Rows.Count == 0 )
                {
                    return false;
                }
                DataRow item = dt.Rows[0];

                _name = Convert.ToString( item["login"] );
                _password = Convert.ToString( item["password"] );
                _lastActive = SqlUtils.toDateTime( item["lastactive"] );
                _accessLevel = SqlUtils.toInt( item["access_level"] );
                _ip = Convert.ToString( item["ip"] );
                _host = Convert.ToString( item["host"] );
                _banned = SqlUtils.toBool( item["banned"] );
                _characterSlot = SqlUtils.toInt( item["character_slot"] );
            }
            _log.fine( "account exists" );
            return true;
        }

        /// <summary>
        /// 最終ログイン日をDBに反映する。
        /// </summary>
        /// <param name="account">アカウント</param>
        public void updateLastActive()
        {/*TTOK*/
            String sqlstr = "UPDATE accounts SET lastactive=?1 WHERE login = ?2";
            using ( MySqlWrapper wrapper = new MySqlWrapper() )
            {
                MySqlCommand cmd = new MySqlCommand( sqlstr );
                cmd.Parameters.Add( new MySqlParameter( "?1", DateTime.Now ) );
                cmd.Parameters.Add( new MySqlParameter( "?2", _name ) );
                wrapper.ExecNonQuery( cmd );
            }
            _log.fine( "update lastactive for " + _name );
        }

        /// <summary>
        /// キャラクター所有数をカウントする。
        /// </summary>
        /// <returns>所有数</returns>
        public int countCharacters()
        {/*TTOK*/
            int result = 0;
            String sqlstr = "SELECT count(*) FROM characters WHERE account_name=?1";
            using ( MySqlWrapper wrapper = new MySqlWrapper() )
            {
                MySqlCommand cmd = new MySqlCommand( sqlstr );
                cmd.Parameters.Add( new MySqlParameter( "?1", _name ) );
                Object obj = wrapper.ExecScaler( cmd );
                if ( obj != null && !( obj is DBNull ) )
                {
                    result = SqlUtils.toInt( obj );
                }
            }
            return result;
        }

        /// <summary>
        /// アカウントを無効にする
        /// </summary>
        /// <returns>更新できたらTrue</returns>
        public bool ban()
        {
            return ban( _name );
        }
        public static bool ban( String accountName )
        {
            int count = 0;
            String sqlstr = "UPDATE accounts SET banned=1 WHERE login=?1";
            using ( MySqlWrapper wrapper = new MySqlWrapper() )
            {
                MySqlCommand cmd = new MySqlCommand( sqlstr );
                cmd.Parameters.Add( new MySqlParameter( "?1", accountName ) );
                count = wrapper.ExecNonQuery( cmd );
            }
            if ( count == 1 )
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 入力されたパスワードとDB上のパスワードを照合する
        /// </summary>
        /// <param name="rawPassword">平文パスワード</param>
        /// <returns></returns>
        public bool validatePassword( String rawPassword )
        {
            // 認証成功後に再度認証された場合は失敗させる。
            if ( _isValid )
            {
                return false;
            }
            String check = encodePassword( rawPassword );
            _isValid = _password.Equals( encodePassword( rawPassword ) );
            //_isValid = _password.EndsWith( rawPassword );
            if ( _isValid )
            {
                _password = null; // 認証が成功した場合、パスワードを破棄する。
            }
            return _isValid;
        }

        /// <summary>
        /// アカウントが有効かどうかを返す(Trueで有効)
        /// </summary>
        /// <returns>有効ならTrue</returns>
        public bool isValid()
        {
            return _isValid;
        }

        /// <summary>
        /// アカウントがゲームマスタかどうか返す(Trueでゲームマスタ)
        /// </summary>
        /// <returns>ゲームマスターならTrue</returns>
        public bool isGameMaster()
        {
            return 0 < _accessLevel;
        }

        /// <summary>
        /// アカウント名を取得する
        /// </summary>
        /// <returns>アカウント名</returns>
        public String getName()
        {
            return _name;
        }

        /// <summary>
        /// 接続先のIPアドレスを取得する
        /// </summary>
        /// <returns>IPアドレス</returns>
        public String getIp()
        {
            return _ip;
        }

        /// <summary>
        /// 最終ログイン日を取得する
        /// </summary>
        /// <returns><see cref="Nullable<DateTIme>"/>最終ログイン日</returns>
        public DateTime getLastActive()
        {
            if ( _lastActive != null )
            {
                return (DateTime)_lastActive;
            }
            else
            {
                return new DateTime( 0 );
            }
        }

        /// <summary>
        /// アクセスレベルを取得する
        /// </summary>
        /// <returns>アクセスレベル</returns>
        public int getAccessLevel()
        {
            return _accessLevel;
        }

        /// <summary>
        /// ホスト名を取得する
        /// </summary>
        /// <returns>ホスト名</returns>
        public String getHost()
        {
            return _host;
        }

        /// <summary>
        /// アクセス禁止情報を取得する
        /// </summary>
        /// <returns>アクセス禁止ならTrue</returns>
        public bool isBanned()
        {
            return _banned;
        }

        /// <summary>
        /// キャラクターの追加スロット数を取得する.
        /// </summary>
        /// <returns>追加スロット数</returns>
        public int getCharacterSlot()
        {
            return _characterSlot;
        }

        /// <summary>
        /// スロット数をDBに反映する。
        /// </summary>
        /// <param name="i">スロット数</param>
        public void setCharacterSlot( int i )
        {/*TTOK*/
            _characterSlot = i;

            String sqlstr = @"UPDATE accounts SET 
                                character_slot=?1 
                                WHERE login=?2";
            using ( MySqlWrapper wrapper = new MySqlWrapper() )
            {
                MySqlCommand cmd = new MySqlCommand( sqlstr );
                cmd.Parameters.Add( new MySqlParameter( "?1", _characterSlot ) );
                cmd.Parameters.Add( new MySqlParameter( "?2", _name ) );
                wrapper.ExecNonQuery( cmd );
            }
            _log.fine( "update characterslot for " + _name );
        }
    }
}
